﻿// Copyright (c) 2007 Readify Pty. Ltd.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Threading;
using BuildMonitor.Properties;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Server;

namespace BuildMonitor.Engine
{
	public class BuildWatcher
	{
		private IBuildPublisher publisher;
		private ConsoleText consoleText;
		public BuildWatcher(ConsoleText consoleText)
		{
			this.consoleText = consoleText;
			publisher = PublisherMethod.GetPublisher(consoleText);
		}

		public BuildWatcher(IBuildPublisher publisher, ConsoleText consoleText)
		{
			this.publisher = publisher;
			this.consoleText = consoleText;
		}

		private T GetService<T>()
		{
			string teamFoundationServerUrl = Settings.Default.TeamFoundationUrl;
			TeamFoundationServer server = TeamFoundationServerFactory.GetServer(teamFoundationServerUrl, new UICredentialsProvider());
			T service = default(T);
			try
			{
				server.EnsureAuthenticated();
				service = (T) server.GetService(typeof (T));
			}
			catch (Exception ex)
			{
				consoleText.WriteLine("Error communication with TFS server: "+teamFoundationServerUrl);
				consoleText.WriteLine(ex.Message);
			}
			return service;
		}

		private Thread workerThread;

		private void Worker()
		{
			var events = GetListOfBuildStoreEvents();
			publisher.Init(events);

			while (true)
			{
				try
				{
					var buildStoreEvents = GetListOfBuildStoreEvents();
					foreach (var buildEvent in buildStoreEvents)
					{
						ProcessBuildEvent(buildEvent);
					}
				}
				catch (ThreadAbortException)
				{
					return;
				}

				Thread.Sleep(Settings.Default.PollPeriod * 1000);
			}
		}

		private void ProcessBuildEvent(BuildStoreEvent buildEvent)
		{
			switch (buildEvent.Type)
			{
				case BuildStoreEventType.Build:
					publisher.BuildCompletionEvent(buildEvent);
					break;
				case BuildStoreEventType.QualityChanged:
					publisher.BuildStatusChangeEvent(buildEvent);
					break;
				default:
					throw new Exception("Event was not recognised.");
			}

		}

		private List<BuildStoreEvent> GetListOfBuildStoreEvents()
		{
			string[] teamProjects = GetListOfTeamProjects();
			List<IBuildDetail> builds = GetBuildsForTeamProjects(teamProjects);

			var buildStoreEventList = new List<BuildStoreEvent>();
			foreach (IBuildDetail build in builds)
			{
				BuildStoreEvent buildStoreEvent = GetBuildStoreEventIfAny(build);
				if (buildStoreEvent != null)
				{
					buildStoreEventList.Add(buildStoreEvent);
				}
			}
			return buildStoreEventList;
		}

		private readonly Dictionary<string, IBuildDetail> cacheLookup = new Dictionary<string, IBuildDetail>();

		private BuildStoreEvent GetBuildStoreEventIfAny(IBuildDetail build)
		{
			BuildStoreEvent buildStoreEvent;
			if (!cacheLookup.ContainsKey(build.Uri.AbsoluteUri))
			{
				cacheLookup.Add(build.Uri.AbsoluteUri, build);

				buildStoreEvent = new BuildStoreEvent
				                  	{
				                  		Type = BuildStoreEventType.Build,
				                  		Data = build
				                  	};
				return buildStoreEvent;
			}

			IBuildDetail originalBuild = cacheLookup[build.Uri.AbsoluteUri];
			cacheLookup[build.Uri.AbsoluteUri] = build;

			if (originalBuild.Quality != build.Quality || originalBuild.Status != build.Status)
			{
				buildStoreEvent = new BuildStoreEvent
				                  	{
				                  		Data = build,
				                  		Type = originalBuild.Quality != build.Quality ? 
				                  		                                              	BuildStoreEventType.QualityChanged : BuildStoreEventType.Build
				                  	};
				return buildStoreEvent;
			}

			return null;
		}

		private List<IBuildDetail> GetBuildsForTeamProjects(IEnumerable<string> teamProjects)
		{
			var buildList = new List<IBuildDetail>();
			var buildServer = GetService<IBuildServer>();
			foreach (string teamProject in teamProjects)
			{
				var definitions = buildServer.QueryBuildDefinitions(teamProject);
				foreach (var definition in definitions)
				{
					if (definition.Enabled)
					{
						var builds = buildServer.QueryBuilds(definition);
						IBuildDetail buildToAdd = null;
						foreach (var build in builds)
						{
							if (buildToAdd == null || build.StartTime > buildToAdd.StartTime)
							{
								buildToAdd = build;
							}
						}
						if (buildToAdd != null)
							buildList.Add(buildToAdd);
					}
				}
			}
			return buildList;
		}

		private string[] GetListOfTeamProjects()
		{
			var structureService = GetService<ICommonStructureService>();
			if (structureService == null)
				return new List<string>().ToArray();
			ProjectInfo[] projectInfoArray = structureService.ListProjects();
			string[] projects = Array.ConvertAll<ProjectInfo, string>(
				projectInfoArray,
				info => info.Name
				);
			return projects;
		}

		public void Start()
		{
			if (workerThread != null)
			{
				throw new Exception("The worker thread has already been started.");
			}

			workerThread = new Thread(Worker);
			workerThread.IsBackground = true;
			workerThread.Start();

		}

		public void Stop()
		{
			if (this.workerThread == null)
			{
				throw new Exception("The worker thread was not started.");
			}

			publisher.Close();
			publisher.Dispose();
			publisher = null;

			workerThread.Abort();
			workerThread = null;
		}
	}
}